/**
 * 
 */
package edu.bucknell.edash;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import ioio.lib.api.AnalogInput;
import ioio.lib.api.PulseInput;
import ioio.lib.api.PulseInput.PulseMode;
import ioio.lib.api.TwiMaster;
import ioio.lib.api.exception.ConnectionLostException;
import ioio.lib.util.BaseIOIOLooper;
import ioio.lib.util.IOIOLooper;
import ioio.lib.util.android.IOIOService;

/**
 * An example IOIO service. While this service is alive, it will attempt to
 * connect to a IOIO and blink the LED. A notification will appear on the
 * notification bar, enabling the user to stop the service.
 */
public class EdashIOIOService extends IOIOService {

	/**
	 * Initializing input pin numbers/addresses
	 * 
	 * These Pins are used by the Two Wire Interface
	 * private static final int DATA_TEMP_BAT = 4;
	 * private static final int CLOCK_TEMP_BAT = 5;
	 * private static final int DATA_TEMP_AMB = 47;
	 * private static final int CLOCK_TEMP_AMB = 48;
	 */
	private static final int MAIN_VOLTAGE_PIN = 45;
	private static final int MAIN_CURRENT_PIN = 44;
	private static final int AUX_VOLTAGE_PIN = 41;
	private static final int AUX_CURRENT_PIN = 42;
	private static final int SPEED_INPUT_PIN = 11;
	private static final int TEMP_BAT_TWI_NUMBER =0;
	private static final int VREF_PIN = 46;
	private static final int AMBIENT_ADDRESS = 0x4A;
	private static final int BATTERY_ADDRESS = 0x4B;
	private static final int BUFFER_SIZE = 100;


	/**
	 * Declaring conversion factors for voltages
	 * 
	 */
	private static final double MAIN_VOLTAGE_FACTOR = ((237+4.02)/4.02);
	private static final double AUX_VOLTAGE_FACTOR = (8.06+2)/2.0;
	private static final double MAIN_CURRENT_FACTOR = (10000.0/(5.0+200.0/3.6432));
	private static final double AUX_CURRENT_FACTOR = 1000/(5.0+200.0/3.6517);
	private static final double SPEED_FACTOR = 3600.0/(32*4000.0);
	private static final double MILLISECONDS_TO_HOURS = 1.0/(1000.0*3600);



	/* (non-Javadoc)
	 * @see ioio.lib.util.android.IOIOService#createIOIOLooper()
	 */
	@Override
	protected IOIOLooper createIOIOLooper() {
		return new BaseIOIOLooper() {

			/**
			 * Declaring IOIO interfaces
			 *
			 */
			private TwiMaster batteryTemperatureInput;
			private AnalogInput mainVoltageInput;
			private AnalogInput mainCurrentInput;
			private AnalogInput auxVoltageInput;
			private AnalogInput auxCurrentInput;
			private PulseInput speedInput;
			private AnalogInput vref; 

			/**
			 * Measured Values
			 */
			private double mainCurrent;
			private long oldTime;



			private Bundle _bundle;
			private double[] dArray;

			
			/* (non-Javadoc)
			 * @see ioio.lib.util.BaseIOIOLooper#setup()
			 */
			@Override
			protected void setup() throws ConnectionLostException, InterruptedException {
				dArray= new double[]{1,2,3,4,5,6,7,8,9,0};
				batteryTemperatureInput = ioio_.openTwiMaster(TEMP_BAT_TWI_NUMBER, TwiMaster.Rate.RATE_100KHz, false);
				mainVoltageInput =  ioio_.openAnalogInput(MAIN_VOLTAGE_PIN);
				mainVoltageInput.setBuffer(BUFFER_SIZE);
				mainCurrentInput = ioio_.openAnalogInput(MAIN_CURRENT_PIN);
				mainCurrentInput.setBuffer(BUFFER_SIZE);
				auxVoltageInput = ioio_.openAnalogInput(AUX_VOLTAGE_PIN);
				auxVoltageInput.setBuffer(BUFFER_SIZE);
				auxCurrentInput = ioio_.openAnalogInput(AUX_CURRENT_PIN);
				auxCurrentInput.setBuffer(BUFFER_SIZE);
				speedInput = ioio_.openPulseInput(SPEED_INPUT_PIN, PulseMode.FREQ);
				vref = ioio_.openAnalogInput(VREF_PIN);
				oldTime = System.currentTimeMillis();

			}

			/* (non-Javadoc)
			 * @see ioio.lib.util.BaseIOIOLooper#loop()
			 */
			@Override
			public void loop() throws ConnectionLostException, InterruptedException {
				/**
				 * Setup for TWI requests
				 * Need an empty request (Just 0) to get temperature
				 * Need 2 bytes for the response
				 */
				byte[] request = new byte[]{0};
				byte[] response = new byte[2];
				long time;
				
				//Get the scale factor for our reference voltage multiply all analog measurments by that scale factor
				double scale = 2.5/vref.getVoltage();
				
				dArray[EdashMain.measurementsEnum.SCALE]=scale; 
				
				//Measure Analog Voltages
				dArray[EdashMain.measurementsEnum.MAIN_VOLTAGE] = scale*MAIN_VOLTAGE_FACTOR *filterAnalogInput(mainVoltageInput);//mainVoltageInput.getVoltage();
				mainCurrent = (scale*MAIN_CURRENT_FACTOR * filterAnalogInput(mainCurrentInput));
				time = System.currentTimeMillis();
				dArray[EdashMain.measurementsEnum.MAIN_CURRENT] =  mainCurrent;

				dArray[EdashMain.measurementsEnum.AUX_VOLTAGE] = scale *AUX_VOLTAGE_FACTOR * filterAnalogInput(auxVoltageInput);
				dArray[EdashMain.measurementsEnum.AUX_CURRENT]=  scale *AUX_CURRENT_FACTOR * filterAnalogInput(auxCurrentInput);

				// Measure Temperature
				batteryTemperatureInput.writeRead(AMBIENT_ADDRESS, false, request, request.length, response,response.length);
				dArray[EdashMain.measurementsEnum.AMBIENT_TEMP] =  convertToFTemp(response);

				batteryTemperatureInput.writeRead(BATTERY_ADDRESS, false, request, request.length, response,response.length);
				dArray[EdashMain.measurementsEnum.BATTERY_TEMP] = convertToFTemp(response);

				//Calculate Charge Removed
				dArray[EdashMain.measurementsEnum.CHARGE_REMOVED] =  calculateChargeRemoved(time-oldTime,mainCurrent);
				oldTime = time;
				
				/**
				 * Make speed Measurements non-blocking by interrupting the thread after 100ms
				 */
				TimerTask speedTimer = new TimerTask(){
					Thread parent = Thread.currentThread();
					public void run(){
						parent.interrupt();
					};
				};
				Timer timer = new Timer();
				timer.schedule(speedTimer,100);
				try{
					dArray[EdashMain.measurementsEnum.SPEED] = SPEED_FACTOR*speedInput.getFrequency();
					timer.cancel();
				}catch(InterruptedException e){
					dArray[EdashMain.measurementsEnum.SPEED] =0;
				}

				
				//  Creating the bundle to be sent to the main activity through the message handler
				_bundle = new Bundle();

				// Sending the message via the handler
				_bundle.putDoubleArray("1", dArray);
				Message msg = Message.obtain();
				msg.setData(_bundle);
				//giving the message a type, this can be used to have multiple types of messages being processed by just one handler
				msg.what = 999;
				// Sending the message
				EdashMain._handler.sendMessage(msg);
				
				//Sleeping so we don't send data every ~20ms
				Thread.sleep(100);
			}
		};
	}

	/**
	 * Converts the data from the PMODtmp2 to a Farenhiet Temperature
	 * @param data -reply from the PMOD
	 * @return The temperature in Farenheit
	 */
	private double convertToFTemp(byte data[]){
		double val = ((data[0]<<8)+data[1])*0.0625/8.0;
		val = val *1.8 +32.0;
		return val;
	}
	
	/**
	 * Performs an averaging filter on all of the data in an AnalogInput's buffer
	 * The size of the filter is dictated by the constant BUFFER_SIZE
	 * @param in the Analog input to be averaged
	 * @return the filtered value
	 */
	private double filterAnalogInput(AnalogInput in){
		double filter = 0;
		for (int i = 0; i<BUFFER_SIZE; i++){
			try {
				filter += in.getVoltageBuffered()/BUFFER_SIZE;
			} catch (InterruptedException e) {
				e.printStackTrace();
				filter = -1;
			} catch (ConnectionLostException e) {
				e.printStackTrace();
				filter = -1;
			}
			
		}
		return filter;
		
	}

	/**
	 * Calculates the amp hours used
	 * @param timeElapsed the time elapsed in miliseconds
	 * @param current the amount current flowing during this time period
	 * @return the amphours used
	 */
	private double calculateChargeRemoved(long timeElapsed, double current){
		return current*(timeElapsed*MILLISECONDS_TO_HOURS);
	}

	/**
	 * Set up for the IOIO Service that can be closed via clicking on it
	 */
	@SuppressWarnings("deprecation")
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		if (intent != null && intent.getAction() != null
				&& intent.getAction().equals("stop")) {
			// User clicked the notification. Need to stop the service.
			nm.cancel(0);
			stopSelf();
		} else {
			// Service starting. Create a notification.
			Notification notification = new Notification(
					R.drawable.ioio_service_image, "Edash IOIO service running",
					System.currentTimeMillis());
			notification
			.setLatestEventInfo(this, "Edash IOIO Service", "Click to stop",
					PendingIntent.getService(this, 0, new Intent(
							"stop", null, this, this.getClass()), 0));
			notification.flags |= Notification.FLAG_ONGOING_EVENT;
			nm.notify(0, notification);
		}
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}

}
